﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace TimeWar.Engine
{
    public class Level : DrawableGameComponent
    {

        #region Fields
        protected GraphicsDevice device;
        protected GraphicsDeviceManager graphics;

        protected Int32 numOfPlayers = 1;
        protected Boolean levelInitialized = false;
        protected Boolean contentLoaded = false;

        protected Camera defaultCamera;
        protected Camera[] viewportCameras;
        protected SceneNode playerNode;
        protected List<SceneNode> sceneNodes;

        protected Viewport defaultViewport;
        protected Viewport[] playerViewports;

        protected LevelConfig levelConfigManager;
        protected AlliedConfig alliedConfigManager;

        #region Background

        private const float BOUNDARY = 16.0f;

        // load and access PositionColor.fx shader
        private Effect positionColorEffect;    // shader object
        private EffectParameter positionColorEffectWVP; // to set display matrix for window

        // load and access Texture.fx shader
        private Effect textureEffect;          // shader object                 
        private EffectParameter textureEffectWVP;       // cumulative matrix w*v*p 
        private EffectParameter textureEffectImage;     // texture parameter

        // vertex types and buffers
        private VertexDeclaration positionColor;
        private VertexDeclaration positionColorTexture;

        // ground vertices and texture
        protected VertexPositionColorTexture[]
            groundVertices = new VertexPositionColorTexture[4];
        protected Texture2D terrainTexture;

        Texture2D frontTexture, backTexture, groundTexture,
                  leftTexture, rightTexture, skyTexture;

        private const float EDGE = BOUNDARY * 2.0f;
        private VertexPositionColorTexture[] skyVertices = new
                VertexPositionColorTexture[4];

        protected float BackgroundScaleX = 1;
        protected float BackgroundScaleY = 1;
        protected float BackgroundScaleZ = 1;

        protected BasicEffect basicEffect;
        protected TerrainConfig terrain;
        private IndexBuffer indexBuffer;    // reference vertices
        private VertexBuffer vertexBuffer;   // vertex storage

        const int NUM_COLS = 257;
        const int NUM_ROWS = 257;

        private VertexDeclaration positionNormalTexture;
        private Texture2D wallTexture;

        protected float SkyScaleX = 1;
        protected float SkyScaleY = 1;
        protected float SkyScaleZ = 1;

        #endregion Background


        #endregion Fields


        public Level(Game game, GraphicsDeviceManager deviceManager) : base(game)
        {
            graphics = deviceManager;
            playerViewports = new Viewport[4];
            viewportCameras = new Camera[4];
            sceneNodes = new List<SceneNode>();

            device = deviceManager.GraphicsDevice;
            defaultViewport = device.Viewport;

            if (numOfPlayers > 1)
            {
                for (int i = 0; i < numOfPlayers; i++)
                {
                    playerViewports[i] = defaultViewport;
                }
                for (int i = 0; i < numOfPlayers; i++)
                {
                    playerViewports[i].Height = playerViewports[i].Height / numOfPlayers;
                }
                playerViewports[1].Y = playerViewports[0].Y - 1;
            }

            levelConfigManager = new LevelConfig();
            alliedConfigManager = new AlliedConfig();

        }

        protected override void LoadContent()
        {

            if (contentLoaded)
            {
                return;
            }

            playerNode.CurrentModel = Game.Content.Load<Model>(alliedConfigManager.Player.Model);
            //playerNode.LoadContent(Game.Content.Load<Model>(alliedConfigManager.Player.Model), Game.Content.Load<Effect>(alliedConfigManager.Player.Effect));

            //crashes on this call.
            playerNode.setAnimationPlayer("Take 001");

            // Load the baseball bat model.
            playerNode.BaseballBat = Game.Content.Load<Model>(alliedConfigManager.Player.BaseballBat);

            // Load the bounding spheres.
            playerNode.SkinnedSpheres = Game.Content.Load<SkinnedSphere[]>(alliedConfigManager.Player.CollisionSpheres);

            playerNode.setSpherePrimitive(new SpherePrimitive(graphics.GraphicsDevice, 1, 12));
            playerNode.Position = alliedConfigManager.Player.StartPosition;

            foreach (AllyConfig allyConfig in alliedConfigManager.Allies)
            {
                SceneNode node = new SceneNode(SceneNodeType.AnimatedCharacter, graphics.GraphicsDevice);
                node.CurrentModel = Game.Content.Load<Model>(allyConfig.Model);
                //node.LoadContent(Game.Content.Load<Model>(allyConfig.Model), Game.Content.Load<Effect>(allyConfig.Effect));
                node.setAnimationPlayer("Take 001");
                node.Position = allyConfig.StartPosition;
                node.Scale = (float)allyConfig.Scale;
                node.BaseballBat = Game.Content.Load<Model>(allyConfig.BaseballBat);
                node.SkinnedSpheres = Game.Content.Load<SkinnedSphere[]>(allyConfig.CollisionSpheres);
                node.setSpherePrimitive(new SpherePrimitive(graphics.GraphicsDevice, 1, 12));
                sceneNodes.Add(node);

            }
            


            InitializeBaseCode();
            InitializeGround();
            terrain = Game.Content.Load<TerrainConfig>(levelConfigManager.HeightMap);
            InitializeIndices();
            InitializeVertexBuffer();
            InitializeBasicEffect();
            InitializeSkybox();


            #region Load Terrain Information
            terrainTexture = Game.Content.Load<Texture2D>(levelConfigManager.TerrainTexture);
            BackgroundScaleX = (float)levelConfigManager.TerrainScaleX;
            BackgroundScaleY = (float)levelConfigManager.TerrainScaleY;
            BackgroundScaleZ = (float)levelConfigManager.TerrainScaleZ;
            wallTexture = Game.Content.Load<Texture2D>(levelConfigManager.TerrainTexture);

            #endregion Load Terrain Information

            #region Load Sky Information

            backTexture = Game.Content.Load<Texture2D>(levelConfigManager.SkyBack);
            frontTexture = Game.Content.Load<Texture2D>(levelConfigManager.SkyFront);
            leftTexture = Game.Content.Load<Texture2D>(levelConfigManager.SkyLeft);
            rightTexture = Game.Content.Load<Texture2D>(levelConfigManager.SkyRight);
            skyTexture = Game.Content.Load<Texture2D>(levelConfigManager.SkyTop);
            SkyScaleX = (float)levelConfigManager.SkyScaleX;
            SkyScaleY = (float)levelConfigManager.SkyScaleY;
            SkyScaleZ = (float)levelConfigManager.SkyScaleZ;

            #endregion Load Sky Information


            defaultCamera.DesiredPositionOffset = levelConfigManager.CameraOffset;
            defaultCamera.NearClip = (float)levelConfigManager.CameraNearClip;
            defaultCamera.FarClip = (float)levelConfigManager.CameraFarClip;

            contentLoaded = true;

        }

        
        #region Terrain

        protected void InitializeBasicEffect()
        {
            basicEffect = new BasicEffect(graphics.GraphicsDevice);
            basicEffect.TextureEnabled = true;             // needed if objects are textured
            basicEffect.LightingEnabled = true;             // must be on for lighting effect
            basicEffect.EnableDefaultLighting();
            basicEffect.SpecularPower = 5.0f;             // highlights
            basicEffect.AmbientLightColor
                = new Vector3(0.6f, 0.6f, 0.5f);            // background light
            basicEffect.DirectionalLight0.Enabled = true;   // turn on light
            basicEffect.DirectionalLight0.DiffuseColor      // diffuse color
                = new Vector3(0.2f, 0.2f, 0.2f);            // rgb range 0 to 1
            basicEffect.DirectionalLight0.SpecularColor     // highlight color
                = new Vector3(0.5f, 0.5f, 0.37f);           // rgb range 0 to 1
            basicEffect.DirectionalLight0.Direction         // set normalized
                = Vector3.Normalize(new Vector3(0.0f, -1.0f, -1.0f));// direction
        }

        protected void InitializeIndices()
        {
            short[] indices;                                             // indices for 1 subset
            indices = new short[2 * NUM_COLS];                   // sized for 1 subset
            indexBuffer = new IndexBuffer(
                                                graphics.GraphicsDevice, // graphics device
                                                typeof(short),           // data type is short
                                                indices.Length,          // array size in bytes
                                                BufferUsage.WriteOnly);  // memory allocation

            // store indices for one subset of vertices
            // see Figure 11-2 for the first subset of indices
            int counter = 0;
            for (int col = 0; col < NUM_COLS; col++)
            {
                indices[counter++] = (short)col;
                indices[counter++] = (short)(col + NUM_COLS);
            }
            indexBuffer.SetData(indices); // store in index buffer
        }

        protected void InitializeVertexBuffer()
        {
            vertexBuffer = new VertexBuffer(
                                graphics.GraphicsDevice,            // graphics device
                                typeof(VertexPositionNormalTexture),// vertex type
                                NUM_COLS * NUM_ROWS,                // element count
                                BufferUsage.WriteOnly);             // memory use

            // store vertices temporarily while initializing them
            VertexPositionNormalTexture[] vertex
                = new VertexPositionNormalTexture[NUM_ROWS * NUM_COLS];

            // set grid width and height
            float colWidth = (float)2 * BOUNDARY / (NUM_COLS - 1);
            float rowHeight = (float)2 * BOUNDARY / (NUM_ROWS - 1);

            // set position, color, and texture coordinates
            for (int row = 0; row < NUM_ROWS; row++)
            {
                for (int col = 0; col < NUM_COLS; col++)
                {
                    vertex[col + row * NUM_COLS].Position       // position
                            = terrain.position[col + row * NUM_COLS];
                    float U = (float)col / (float)(NUM_COLS - 1); // UV
                    float V = (float)row / (float)(NUM_ROWS - 1);
                    vertex[col + row * NUM_COLS].TextureCoordinate
                            = new Vector2(U, V);
                    vertex[col + row * NUM_COLS].Normal     // normal
                            = terrain.normal[col + row * NUM_COLS];
                }
            }
            // commit data to vertex buffer
            vertexBuffer.SetData(vertex);
        }

        
        /// <summary>
        /// This method is called when the program begins to set game application
        /// properties such as status bar title and draw mode.  It initializes the  
        /// camera viewer projection, vertex types, and shaders.
        /// </summary>
        protected void InitializeBaseCode()
        {
            // set status bar in PC Window (there is none for the Xbox 360)

            // see both sides of objects drawn
            //graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;

            // initialize vertex types
            //positionColor = new VertexDeclaration(graphics.GraphicsDevice,
             //                             VertexPositionColor.VertexElements);
            positionColor = new VertexDeclaration(new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Color, 0));
            //positionColorTexture = new VertexDeclaration(graphics.GraphicsDevice,
            //                              VertexPositionColorTexture.VertexElements);
            positionColorTexture = new VertexDeclaration(new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.TextureCoordinate, 0));

            // load PositionColor.fx and set global params
            positionColorEffect = Game.Content.Load<Effect>(levelConfigManager.TerrainColourEffect);
            positionColorEffectWVP = positionColorEffect.Parameters["wvpMatrix"];

            // load Texture.fx and set global params
            textureEffect = Game.Content.Load<Effect>(levelConfigManager.TerrainAssetName);
            textureEffectWVP = textureEffect.Parameters["wvpMatrix"];
            textureEffectImage = textureEffect.Parameters["textureImage"];

            //positionNormalTexture = new VertexDeclaration(graphics.GraphicsDevice,
            //                VertexPositionNormalTexture.VertexElements);
            positionNormalTexture = new VertexDeclaration(new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0));

        }




        /// <summary>
        /// Set vertices for rectangular surface that is drawn using a triangle strip.
        /// </summary>
        protected void InitializeGround()
        {
            const float BORDER = BOUNDARY;
            Vector2 uv = new Vector2(0.0f, 0.0f);
            Vector3 pos = new Vector3(0.0f, 0.0f, 0.0f);
            Color color = Color.White;

            // top left
            uv.X = 0.0f; uv.Y = 0.0f; pos.X = -BORDER; pos.Y = 0.0f; pos.Z = -BORDER;
            groundVertices[0] = new VertexPositionColorTexture(pos, color, uv);

            // bottom left
            uv.X = 0.0f; uv.Y = 1.0f; pos.X = -BORDER; pos.Y = 0.0f; pos.Z = BORDER;
            groundVertices[1] = new VertexPositionColorTexture(pos, color, uv);

            // top right
            uv.X = 1.0f; uv.Y = 0.0f; pos.X = BORDER; pos.Y = 0.0f; pos.Z = -BORDER;
            groundVertices[2] = new VertexPositionColorTexture(pos, color, uv);

            // bottom right
            uv.X = 1.0f; uv.Y = 1.0f; pos.X = BORDER; pos.Y = 0.0f; pos.Z = BORDER;
            groundVertices[3] = new VertexPositionColorTexture(pos, color, uv);
        }

        protected void HandleOffHeightMap(ref int row, ref int col)
        {
            if (row >= terrain.NUM_ROWS)
                row = terrain.NUM_ROWS - 2;
            else if (row < 0)
                row = 0;
            if (col >= terrain.NUM_COLS)
                col = terrain.NUM_COLS - 2;
            else if (col < 0)
                col = 0;
        }

        protected Vector3 CellNormal(int row, int col)
        {
            HandleOffHeightMap(ref row, ref col);

            return terrain.normal[col + row * terrain.NUM_COLS];
        }

        Vector3 Normal(Vector3 position)
        {
            // coordinates for top left of cell
            Vector3 cellPosition = RowColumn(position);
            int row = (int)cellPosition.Z;
            int col = (int)cellPosition.X;
            // distance from top left of cell
            float distanceFromLeft = position.X % terrain.cellWidth;
            float distanceFromTop = position.Z % terrain.cellHeight;

            // use lerp to interpolate normal at point within cell
            Vector3 topNormal = Vector3.Lerp(CellNormal(row, col),
                                                   CellNormal(row, col + 1),
                                                   distanceFromLeft);
            Vector3 bottomNormal = Vector3.Lerp(CellNormal(row + 1, col),
                                                   CellNormal(row + 1, col + 1),
                                                   distanceFromLeft);
            Vector3 normal = Vector3.Lerp(topNormal,
                                                   bottomNormal,
                                                   distanceFromTop);
            normal.Normalize(); // convert to unit vector for consistency
            return normal;
        }

        Vector3 NormalWeight(Vector3 position, Vector3 speed,
                             float numCells, float directionScalar)
        {
            float weight = 0.0f;
            float startWeight = 0.0f;
            float totalSteps = (float)numCells;
            Vector3 nextPosition;
            Vector3 cumulativeNormal = Vector3.Zero;

            for (int i = 0; i <= numCells; i++)
            {
                // get position in next cell
                nextPosition = ProjectedXZ(position, speed, directionScalar);
                if (i == 0)
                {
                    // current cell
                    startWeight = CellWeight(position, nextPosition);
                    weight = startWeight / totalSteps;
                }
                else if (i == numCells) // end cell
                    weight = (1.0f - startWeight) / totalSteps;
                else                    // all cells in between
                    weight = 1.0f / totalSteps;

                cumulativeNormal += weight * Normal(position);
                position = nextPosition;
            }
            cumulativeNormal.Normalize();
            return cumulativeNormal;
        }

        Vector3 ProjectedUp(Vector3 position, Vector3 speed, int numCells)
        {
            Vector3 frontAverage, backAverage, projectedUp;

            // total steps must be 0 or more. 0 steps means no shock absorption.
            if (numCells <= 0)
                return Normal(position);

            // weighted average of normals ahead and behind enable smoother ride.
            else
            {
                frontAverage = NormalWeight(position, speed, numCells, 1.0f);
                backAverage = NormalWeight(position, speed, numCells, -1.0f);
            }
            projectedUp = (frontAverage + backAverage) / 2.0f;
            projectedUp.Normalize();
            return projectedUp;
        }

        Vector3 RowColumn(Vector3 position)
        {
            // calculate X and Z
            int col = (int)((position.X + terrain.worldWidth) / terrain.cellWidth);
            int row = (int)((position.Z + terrain.worldHeight) / terrain.cellHeight);
            HandleOffHeightMap(ref row, ref col);

            return new Vector3(col, 0.0f, row);
        }

        float Height(int row, int col)
        {
            HandleOffHeightMap(ref row, ref col);
            return terrain.position[col + row * terrain.NUM_COLS].Y;
        }

        public float CellHeight(Vector3 position)
        {
            // get top left row and column indicies
            Vector3 cellPosition = RowColumn(position);
            int row = (int)cellPosition.Z;
            int col = (int)cellPosition.X;

            // distance from top left of cell
            float distanceFromLeft, distanceFromTop;
            distanceFromLeft = position.X % terrain.cellWidth;
            distanceFromTop = position.Z % terrain.cellHeight;

            // lerp projects height relative to known dimensions
            float topHeight = MathHelper.Lerp(Height(row, col),
                                                  Height(row, col + 1),
                                                  distanceFromLeft);
            float bottomHeight = MathHelper.Lerp(Height(row + 1, col),
                                                  Height(row + 1, col + 1),
                                                  distanceFromLeft);
            return MathHelper.Lerp(topHeight, bottomHeight, distanceFromTop);
        }

        //private void DisplayCurrentHeight()
        //{
        //    string outputString;
        //    Rectangle safeArea;

        //    // start drawing font sprites
        //    spriteBatch.Begin(SpriteBlendMode.AlphaBlend, // enable transparency
        //                        SpriteSortMode.Immediate,  // use manual order
        //                        SaveStateMode.SaveState);  // store 3D settings

        //    Vector3 position = RowColumn(defaultCamera.Position);
        //    int row = (int)position.Z;
        //    int col = (int)position.X;
        //    float height = terrain.position[col + terrain.NUM_ROWS * row].Y;

        //    // show cell height and width
        //    outputString = "Cell Height=" + height;
        //    safeArea = TitleSafeRegion(outputString, spriteFont);
        //    spriteBatch.DrawString(spriteFont, outputString, new Vector2(
        //                           safeArea.Left, safeArea.Top), Color.Yellow);
        //    // stop drawing - and 3D settings are restored if SaveState used
        //    spriteBatch.End();
        //}

        public Vector3 ProjectedXZ(Vector3 position, Vector3 speed,
                           float directionScalar)
        {
            // only consider change in X and Z when projecting position
            // in neighboring cell.
            Vector3 velocity = new Vector3(speed.X, 0.0f, speed.Z);
            velocity = Vector3.Normalize(velocity);
            float changeX = directionScalar * terrain.cellWidth * velocity.X;
            float changeZ = directionScalar * terrain.cellHeight * velocity.Z;

            return new Vector3(position.X + changeX, 0.0f, position.Z + changeZ);
        }

        protected float CellWeight(Vector3 currentPosition, Vector3 nextPosition)
        {
            Vector3 currRowColumn = RowColumn(currentPosition);
            int currRow = (int)currRowColumn.Z;
            int currCol = (int)currRowColumn.X;
            Vector3 nextRowColumn = RowColumn(nextPosition);
            int nextRow = (int)nextRowColumn.Z;
            int nextCol = (int)nextRowColumn.X;

            // find row and column between current cell and neighbor cell
            int rowBorder, colBorder;
            if (currRow < nextRow)
                rowBorder = currRow + 1;
            else
                rowBorder = currRow;

            if (currCol < nextCol)              // next cell at right of current cell
                colBorder = currCol + 1;
            else
                colBorder = currCol;            // next cell at left of current cell
            Vector3 intersect = Vector3.Zero;   // margins between current
            // and next cell

            intersect.X = -BOUNDARY + colBorder * terrain.cellWidth;
            intersect.Z = -BOUNDARY + rowBorder * terrain.cellHeight;
            currentPosition.Y
                          = 0.0f;               // not concerned about height
            // find distance between current position and cell border
            Vector3 difference = intersect - currentPosition;
            float lengthToBorder = difference.Length();

            // find distance to projected location in neighboring cell
            difference = nextPosition - currentPosition;
            float lengthToNewCell = difference.Length();
            if (lengthToNewCell == 0)              // prevent divide by zero
                return 0.0f;

            // weighted distance in current cell relative to the entire
            // distance to projected position
            return lengthToBorder / lengthToNewCell;
        }


        #endregion Terrain


        #region Sky


        protected void InitializeSkybox()
        {
            Vector3 pos = Vector3.Zero;
            Vector2 uv = Vector2.Zero;
            Color color = Color.White;

            const float MAX = 0.997f; // offset to remove white seam at image edge
            const float MIN = 0.003f; // offset to remove white seam at image edge

            // set position, image, and color data for each vertex in rectangle

            pos.X = +EDGE; pos.Y = -EDGE; uv.X = MIN; uv.Y = MAX; // Bottom R
            skyVertices[0] = new VertexPositionColorTexture(pos, color, uv);

            pos.X = +EDGE; pos.Y = +EDGE; uv.X = MIN; uv.Y = MIN; // Top R
            skyVertices[1] = new VertexPositionColorTexture(pos, color, uv);

            pos.X = -EDGE; pos.Y = -EDGE; uv.X = MAX; uv.Y = MAX; // Bottom L
            skyVertices[2] = new VertexPositionColorTexture(pos, color, uv);

            pos.X = -EDGE; pos.Y = +EDGE; uv.X = MAX; uv.Y = MIN; // Top L
            skyVertices[3] = new VertexPositionColorTexture(pos, color, uv);
        }


        #endregion Sky


        public override void Initialize()
        {

            if (levelInitialized)
            {
                return;
            }

            defaultCamera.Position = Vector3.Add(playerNode.Position, defaultCamera.DesiredPositionOffset);

            base.Initialize();

            levelInitialized = true;

        }

        public override void Update(GameTime gameTime)
        {

            defaultCamera.Update(gameTime);
            playerNode.UpdateCamera(defaultCamera);
            playerNode.Update(gameTime);
            defaultCamera.UpdateChasedObject(playerNode.Position, playerNode.Rotation);

            if (null != sceneNodes && sceneNodes.Count > 0)
            {
                foreach (SceneNode node in sceneNodes)
                {
                    node.UpdateCamera(defaultCamera);
                    node.Update(gameTime);
                }
            }

            base.Update(gameTime);

        }


        
        public override void Draw(GameTime gameTime)
        {

            base.Draw(gameTime);

            //graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
            //graphics.GraphicsDevice.DepthStencilState.DepthBufferWriteEnable = false;
            DepthStencilState depthStencilState = new DepthStencilState();
            depthStencilState.DepthBufferWriteEnable = false;
            graphics.GraphicsDevice.DepthStencilState = depthStencilState;


            #region Draw Background

            //DrawGround();
            DrawSkybox();
            DrawIndexedGrid("ground");

            #endregion Draw Background

            //graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            //graphics.GraphicsDevice.RasterizerState.CullMode = CullMode.CullCounterClockwiseFace;
            RasterizerState rasterizerStateBackground = new RasterizerState();
            rasterizerStateBackground.CullMode = CullMode.CullCounterClockwiseFace;
            graphics.GraphicsDevice.RasterizerState = rasterizerStateBackground;


            if (null != sceneNodes && sceneNodes.Count > 0)
            {
                foreach (SceneNode node in sceneNodes)
                {
                    node.Draw(gameTime);
                }
            }


            if (numOfPlayers > 1)
            {
                foreach (Viewport playerView in playerViewports)
                {
                    device.Viewport = playerView;
                    playerNode.Draw(gameTime);
                }
            }
            else
            {
                playerNode.Draw(gameTime);
            }

            //for testing purposes
            playerNode.DrawBaseballBat(defaultCamera.View, defaultCamera.Projection);

            //graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;
            //graphics.GraphicsDevice.DepthStencilState.DepthBufferEnable = true;
            //graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
            //graphics.GraphicsDevice.DepthStencilState.DepthBufferWriteEnable = true;
            DepthStencilState drawDepthStencilState = new DepthStencilState();
            drawDepthStencilState.DepthBufferEnable = true;
            drawDepthStencilState.DepthBufferWriteEnable = true;
            graphics.GraphicsDevice.DepthStencilState = drawDepthStencilState;




        }


        #region Terrain Drawing

        /// <summary>
        /// Draws colored surfaces with PositionColor.fx shader. 
        /// </summary>
        /// <param name="primitiveType">Object type drawn with vertex data.</param>
        /// <param name="vertexData">Array of vertices.</param>
        /// <param name="numPrimitives">Total primitives drawn.</param>
        private void PositionColorShader(PrimitiveType primitiveType,
                                         VertexPositionColor[] vertexData,
                                         int numPrimitives)
        {
            //positionColorEffect.Begin(); // begin using PositionColor.fx
            //positionColorEffect.Techniques[0].Passes[0].Begin();

            // set drawing format and vertex data then draw primitive surface
            //graphics.GraphicsDevice.VertexDeclaration = positionColor;
            //graphics.GraphicsDevice.V
            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                                    primitiveType, vertexData, 0, numPrimitives);

            //positionColorEffect.Techniques[0].Passes[0].End();
            //positionColorEffect.End();  // stop using PositionColor.fx
        }

        /// <summary>
        /// Draws textured primitive objects using Texture.fx shader. 
        /// </summary>
        /// <param name="primitiveType">Object type drawn with vertex data.</param>
        /// <param name="vertexData">Array of vertices.</param>
        /// <param name="numPrimitives">Total primitives drawn.</param>
        private void TextureShader(PrimitiveType primitiveType,
                                   VertexPositionColorTexture[] vertexData,
                                   int numPrimitives)
        {
            //textureEffect.Begin(); // begin using Texture.fx
            //textureEffect.Techniques[0].Passes[0].Begin();
            textureEffect.CurrentTechnique.Passes[0].Apply();

            // set drawing format and vertex data then draw surface
            //graphics.GraphicsDevice.VertexDeclaration = positionColorTexture;
            //basicEffect.CurrentTechnique.Passes[0].Apply();
            graphics.GraphicsDevice.DrawUserPrimitives
                                    <VertexPositionColorTexture>(
                                    primitiveType, vertexData, 0, numPrimitives);

            //textureEffect.Techniques[0].Passes[0].End();
            //textureEffect.End(); // stop using Textured.fx
        }

        /// <summary>
        /// Triggers drawing of ground with texture shader.
        /// </summary>
        private void DrawGround()
        {
            // 1: declare matrices
            Matrix world, translation;
            Matrix scale = Matrix.CreateScale(BackgroundScaleX, BackgroundScaleY, BackgroundScaleZ);

            // 2: initialize matrices
            translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f);

            // 3: build cumulative world matrix using I.S.R.O.T. sequence
            // identity, scale, rotate, orbit(translate & rotate), translate
            world = scale * translation;

            // 4: set shader parameters
            textureEffectWVP.SetValue(world * defaultCamera.View * defaultCamera.Projection);
            textureEffectImage.SetValue(groundTexture);

            // 5: draw object - primitive type, vertex data, # primitives
            TextureShader(PrimitiveType.TriangleStrip, groundVertices, 2);

        }

        private void DrawIndexedGrid(string surfaceName)
        {
            if (String.IsNullOrEmpty(surfaceName))
            {
                return;
            }

            // 1: declare matrices
            Matrix world, translate, rotationX, scale, rotationY;

            // 2: initialize matrices
            scale = Matrix.CreateScale(BackgroundScaleX, BackgroundScaleY, BackgroundScaleZ);
            rotationY = Matrix.CreateRotationY(0.0f);
            rotationX = Matrix.CreateRotationX(0.0f);
            translate = Matrix.CreateTranslation(0.0f, -3.6f, 0.0f);

            // create two walls with normals that face the user
            if (surfaceName.Equals("wall"))
            {
                rotationX = Matrix.CreateRotationX(MathHelper.Pi / 2.0f);
                translate = Matrix.CreateTranslation(0.0f, 9.20f, -12.8f);
                basicEffect.Texture = wallTexture;
            }
            else if (surfaceName.Equals("ground"))
                basicEffect.Texture = terrainTexture;// set ground image

            // 3: build cumulative world matrix using I.S.R.O.T. sequence
            // identity, scale, rotate, orbit(translate & rotate), translate
            world = Matrix.Identity * scale * rotationX * rotationY * translate;

            // 4: finish setting shader variables
            basicEffect.World = world;
            basicEffect.Projection = defaultCamera.Projection;
            basicEffect.View = defaultCamera.View;

            // 5: draw object
            //graphics.GraphicsDevice.VertexDeclaration = positionNormalTexture;
            //graphics.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0,
            //VertexPositionNormalTexture.SizeInBytes);
            graphics.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            graphics.GraphicsDevice.Indices = indexBuffer;

            // avoid drawing back face for large numbers of vertices
            //graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            //graphics.GraphicsDevice.RasterizerState.CullMode = CullMode.CullClockwiseFace;
            RasterizerState rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.CullClockwiseFace;
            graphics.GraphicsDevice.RasterizerState = rasterizerState;

            //basicEffect.Begin();
            

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                //pass.Begin();
                pass.Apply();
                //graphics.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0,
                //         VertexPositionNormalTexture.SizeInBytes);
                graphics.GraphicsDevice.SetVertexBuffer(vertexBuffer);
                // draw grid one row at a time
                for (int Z = 0; Z < NUM_ROWS - 1; Z++)
                {
                    graphics.GraphicsDevice.DrawIndexedPrimitives(
                             PrimitiveType.TriangleStrip, // primitive type
                             Z * NUM_COLS,                // start point in vertex buffer
                             0,                           // vertex buffer offset
                             NUM_COLS * NUM_ROWS,         // total verts in vertex buffer
                             0,                           // index buffer offset
                             2 * (NUM_COLS - 1));         // index buffer end
                }
                //pass.End();
            }

            //basicEffect.End();

            // disable culling
            //graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
            //graphics.GraphicsDevice.RasterizerState.CullMode = CullMode.None;
            RasterizerState rasterizerStateEnd = new RasterizerState();
            rasterizerStateEnd.CullMode = CullMode.None;
            graphics.GraphicsDevice.RasterizerState = rasterizerStateEnd;

        }




        #endregion Terrain Drawing


        #region Sky

        private void DrawSkybox()
        {
            const float DROP = -1.2f;

            // 1: declare matrices and set defaults
            Matrix world;
            Matrix scale = Matrix.CreateScale(SkyScaleX, SkyScaleY, SkyScaleZ);
            Matrix rotationY = Matrix.CreateRotationY(0.0f);
            Matrix rotationX = Matrix.CreateRotationX(0.0f);
            Matrix translation = Matrix.CreateTranslation(0.0f, 0.0f, 0.0f);
            Matrix camTranslation // move skybox with camera
                                = Matrix.CreateTranslation(
                                  defaultCamera.Position.X, 0.0f, defaultCamera.Position.Z);

            // 2: set transformations and also texture for each wall
            for (int i = 0; i < 5; i++)
            {
                switch (i)
                {
                    case 0: // BACK
                        translation = Matrix.CreateTranslation(0.0f, DROP, EDGE);
                        textureEffectImage.SetValue(backTexture);
                        break;
                    case 1: // RIGHT
                        translation = Matrix.CreateTranslation(-EDGE, DROP, 0.0f);
                        rotationY = Matrix.CreateRotationY(-(float)Math.PI / 2.0f);
                        textureEffectImage.SetValue(rightTexture);
                        break;
                    case 2: // FRONT
                        translation = Matrix.CreateTranslation(0.0f, DROP, -EDGE);
                        rotationY = Matrix.CreateRotationY((float)Math.PI);
                        textureEffectImage.SetValue(frontTexture);
                        break;
                    case 3: // LEFT
                        translation = Matrix.CreateTranslation(EDGE, DROP, 0.0f);
                        rotationY = Matrix.CreateRotationY((float)Math.PI / 2.0f);
                        textureEffectImage.SetValue(leftTexture);
                        break;
                    case 4: // SKY
                        translation = Matrix.CreateTranslation(0.0f, EDGE + DROP, 0.0f);
                        rotationX = Matrix.CreateRotationX(-(float)Math.PI / 2.0f);
                        rotationY =
                        Matrix.CreateRotationY(3.0f * MathHelper.Pi / 2.0f);
                        textureEffectImage.SetValue(skyTexture);
                        break;
                }
                // 3: build cumulative world matrix using I.S.R.O.T. sequence
                world = scale * rotationX * rotationY * translation * camTranslation;
                //world = rotationX * rotationY * translation * camTranslation;

                // 4: set shader variables
                textureEffectWVP.SetValue(world * defaultCamera.View
                                                * defaultCamera.Projection);

                // 5: draw object - primitive type, vertices, # primitives
                TextureShader(PrimitiveType.TriangleStrip, skyVertices, 2);


            }
        }


        #endregion Sky


    }
}
